#include "pvm.h"
#include "instructions.h"

/**
 * Constructeur de structure de données
 *
 */
PVM* newPVM()
{
	// Allouer l'espace nécessaire à la création d'une PVM
	//
	PVM* newPVM = malloc(sizeof(PVM));

	newPVM->mem = malloc(sizeof(Mot)*MEMSIZE);

	newPVM->pc = PC_START;
	newPVM->sp = SP_START;
	newPVM->sr = SR_START;

	//Verifier l'incrémentation de la pile
	//
	if (SP_START + SP_INCR > MEMSIZE || SP_START + SP_INCR <= 0) {
		printf("L'incrémentation de la pile n'est pas correctement configurée");
	}
	else {

		unsigned int i;

		//Initialiser les registres
		//
		for (i= 0; i < NREGS; i++)
			newPVM->reg[i] = 0;
		//Initialiser les composantes brutes des mots de la mémoire
		//
		for (i = 0; i < MEMSIZE; i++)
			newPVM->mem[i].brut = 0;

		printf("PVM correctement initialisée !\n");
	}

	return newPVM;
}

/**
 * Vérifier l'accès à la mémoire courante
 *
 * @param index		la mémoire cible
 *
 * @returns true si la mémoire est accessible, false sinon
 *
 */
bool verifierAccesMemoire(REG *index){

	if (*index > MEMSIZE) {
		printf("Erreur de segmentation - tentative d'accès à une mémoire non-autorisée");
		return false;
	}
	return true;
}

/**
 * Vérifier l'accès au registre courant
 *
 * @param index		le registre cible
 *
 * @returns true si le registre est valide, false sinon
 *
 */
bool verifierAccesRegistre(REG index){

	if (index >= NREGS) {
		printf("Erreur de segmentation - tentative d'accès à un registre non disponible");
		printf("\nNombre de registres disponibles : %d ", NREGS);
		return false;
	}
	return true;
}

/**
 * Traiter la prochaine instruction
 *
 * @param pvm		la pvm cible
 */
bool step(PVM *pvm)
{

	// Vérifier la disponibilité à l'accès mémoire
	//
	if(!verifierAccesMemoire(&pvm->pc))
		return false;

	// Récupérer le mot correspondant à la prochaine instruction à exécuter
	//
	Mot *m = &pvm->mem[pvm->pc];

	// Arrêter la machine virtuelle dans le cas de rencontre d'un "HALT"
	//
	if (m->codage.codeop == HALT) {
		printf("Instruction HALT rencontrée.\nArrêt de la machine virtuelle");
		return false;
	}

	// Exécuter l'instruction cible
	//
	if(!exec(pvm, m))
		return false;

	// Se déplacer vers la prochaine instruction
	//
	pvm->pc++;

	// Afficher l'état des registres
	//
	printf("########### Etat des registres : ###########\n");
	pvm_status(pvm);


	return true;
}

/**
 * Associer les modes d'adressage d'une instruction à la source et à la destination
 *
 * @param	mot				le mot auquel on attribue les modes d'adressage
 * @param	source			pointeur vers la variable dont le mode doit être modifié (source)
 * @param	destintation	pointeur vers la variable dont le mode doit être modifié (destination)
 *
 * @return true si le mode d'adressage a été trouvé, faux sinon
 */
void getModes(Mot *mot, mode *destination, mode *source)
{
	switch (mot->codage.mode) {
	case REGREG:
		*destination = REGISTER;
		*source = REGISTER;
		break;
	case REGIMM:
		*destination = REGISTER;
		*source = IMMEDIATE;
		break;
	case REGDIR:
		*destination = REGISTER;
		*source = DIRECT;
		break;
	case REGIND:
		*destination = REGISTER;
		*source = INDIRECT;
		break;
	case DIRIMM:
		*destination = DIRECT;
		*source = IMMEDIATE;
		break;
	case DIRREG:
		*destination = DIRECT;
		*source = REGISTER;
		break;
	case INDIMM:
		*destination = INDIRECT;
		*source = IMMEDIATE;
		break;
	case INDREG:
		*destination = INDIRECT;
		*source = REGISTER;
		break;
	default:
		printf("Mode d'adressage invalide");
	}
}

/**
 * Transformer la source en un mot
 *
 * @param PVM		La pvm cible
 * @param mot		Le mot cible
 *
 * @return la valeur de l'opérande source
 *
 */
Mot getSource(PVM *pvm, Mot *mot)
{
	mode destination, source;

	// Décortiquer le mot et obtenir pour obtenir les modes d'adressage répartis
	//
	getModes(mot, &destination, &source);

	// Traiter les actions de manière séparée
	//
	switch (source) {
	case REGISTER:
		// Simuler l'accès en mémoire du registre selectionné
		//
		if (!verifierAccesRegistre(mot->codage.dest))
			return (Mot) {.codage = { HALT }};
		else
			return (Mot) pvm->reg[mot->codage.source];
		break;
	case IMMEDIATE:
		pvm->pc++;
		// Récupérer la valeur à l'adresse de la valeur du PC
		//
		return pvm->mem[pvm->pc];
		break;
	case DIRECT:
		pvm->pc++;
		if(!verifierAccesMemoire(&pvm->mem[pvm->pc].brut))
			return (Mot) {.codage = { HALT }};
		else
			// Récupérer l'opérande située dans l'adresse mémoire
			//
			return (Mot) pvm->mem[pvm->mem[pvm->pc].brut].brut;
		break;
	case INDIRECT:
		// Récupérer l'opérande située dans l'adresse mémoire contenue par le registre
		//
		if(!verifierAccesMemoire(&pvm->reg[mot->codage.dest]))
			return (Mot) {.codage = { HALT }};
		else
			return pvm->mem[pvm->reg[mot->codage.source]];
		break;
	default:
		// Traiter le cas d'un mode d'adressage invalide
		// Arrêter le programme
		//
		printf("Mode d'adressage source invalide");
		return (Mot) {.codage = { HALT }};
	}
}

/**
 * Transformer la destination en un mot
 *
 * @param PVM		La pvm cible
 * @param mot		Le mot cible
 *
 * @return un pointeur vers l'opérande de destination
 *
 */
REG* getDestination(PVM *pvm, Mot *mot)
{
	mode destination, source;

	// Décortiquer le mot et obtenir pour obtenir les modes d'adressage répartis
	//
	getModes(mot, &destination, &source);

	switch (destination) {
	case REGISTER:
		if(!verifierAccesRegistre(mot->codage.dest))
			return NULL;
		else
			return &pvm->reg[mot->codage.dest];
		break;
	case IMMEDIATE:
		printf("Mode d'adressage destination invalide");
		return NULL;
		break;
	case DIRECT:
		pvm->pc++;
		if(!verifierAccesMemoire(&pvm->mem[pvm->pc].brut))
			return NULL;
		else
			return &(pvm->mem[pvm->mem[pvm->pc].brut].brut);
		break;
	case INDIRECT:
		if(!verifierAccesMemoire(&pvm->reg[mot->codage.dest]))
			return NULL;
		else
			return &(pvm->mem[pvm->reg[mot->codage.dest]].brut);
		break;
	default:
		printf("Mode d'adressage destination invalide");
		return NULL;
	}
}

/**
 * Exécuter l'instruction cible
 *
 * @param pvm		la pvm cible
 * @param mot 		le mot cible
 *
 * @return true si l'instruction a bien été exécutée, faux sinon
 *
 */
bool exec(PVM *pvm, Mot *mot)
{
	// Récupérer l'instruction courante
	//
	Instruction instruction = getInstruction(*mot);

	// Récupérer les composantes sources
	//
	Mot source = getSource(pvm, mot);

	// Récupérer les composantes destinations
	//
	REG *dest = getDestination(pvm, mot);

	// Associer la bonne fonction par pointage pour effectuer le traitement
	//
	if ((*(instruction.fonction))(pvm, dest, source)) {
		printf("Instruction successful\n");
		return true;
	} else {
		printf("Instruction unsuccessful\n");
		return false;
	}
}

/**
 * Affiche l'état courant des registres
 *
 */
void pvm_status(PVM *pvm)
{
	pvm_print_registre(pvm, PC);
	pvm_print_registre(pvm, SR);
	pvm_print_registre(pvm, SP);
	unsigned int i = 0;
	for (i = 0; i < NREGS; ++i)
		pvm_print_registre(pvm, i);
}

/**
 * Affiche l'état de la mémoire à l'adresse donnée
 *
 * @param pvm		la pvm cible
 * @param mem 		l'adresse de mémoire cible
 *
 */
bool pvm_print_memoire(PVM *pvm, unsigned int mem)
{
	if (mem >= MEMSIZE) return false;
	printf("MEM[%d] = %d\n", mem, pvm->mem[mem].brut);
	return true;
}

/**
 * Affiche l'état d'un registre
 *
 * @param pvm		la pvm cible
 * @param reg 		le registre cible
 *
 */
bool pvm_print_registre(PVM *pvm, unsigned int reg)
{
	switch (reg) {
	case PC:
		printf("PC = %d\n", pvm->pc);
		break;
	case SR:
		printf("SR = %d\n", pvm->sr);
		break;
	case SP:
		printf("SP = %d\n", pvm->sp);
		break;
	default:
		if (reg < NREGS) {
			printf("R%d = %d\n", reg, pvm->reg[reg]);
		}
		else {
			return false;
		}
		break;
	}
	return true;
}
